/**
 * ReflectUtil.java
 */
package com.mht2017.common.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author yutong.xiao
 * @date 2017年7月14日 下午4:26:12
 */
public class ReflectUtil {

	private static final Logger LOGGER = LoggerFactory.getLogger(ReflectUtil.class);

	public static void setField(Object object, String fieldName, Object value) {
		if (null == object || null == fieldName) {
			return;
		}

		Field declaredField = null;
		boolean accessible = false;
		try {
			@SuppressWarnings("rawtypes")
			Class clazz = object instanceof Class ? (Class) object : object.getClass();
			declaredField = clazz.getDeclaredField(fieldName);
			accessible = declaredField.isAccessible();
			if (accessible == false) {
				declaredField.setAccessible(true);
			}
			declaredField.set(object, value);
		} catch (Exception e) {
			LOGGER.error(String.format("在对象'%s'上设置字段'%s'为'%s'出错: '%s'", object, fieldName, value, e.getMessage()), e);
		} finally {
			if (null != declaredField && accessible == false) {
				declaredField.setAccessible(accessible);
			}
		}
	}

	public static Object invoke(Object object, String methodName, Object... args) {
		if (null == object || null == methodName) {
			return null;
		}

		List<Class<?>> parameterTypes = new ArrayList<>(SizeUtil.size(args));
		if (null != args) {
			for (int i = 0; i < args.length; i++) {
				Object arg = args[i];
				if (null == arg) {
					LOGGER.error(String.format("参数(arg%s)不可以为null, 方法: '%s.%s(..)'", i + 1, object.getClass().getName(), methodName));
					return null;
				}
				parameterTypes.add(arg.getClass());
			}
		}

		Method targetMethod = pickMethod(object, methodName, parameterTypes, args);

		boolean accessible = false;
		try {
			if (null != targetMethod) {
				accessible = targetMethod.isAccessible();
				if (accessible == false) {
					targetMethod.setAccessible(true);
				}
				return targetMethod.invoke(object, args);
			}
		} catch (Exception e) {
			LOGGER.error(String.format("在对象'%s'上执行方法'%s'出错: '%s'", object, methodName, e.getMessage()), e);
			throw new RuntimeException(e);
		} finally {
			if (null != targetMethod && accessible == false) {
				targetMethod.setAccessible(accessible);
			}
		}
		return null;
	}

	private static Method pickMethod(Object object, String methodName, List<Class<?>> parameterTypes, Object... args) {
		try {
			return object.getClass().getDeclaredMethod(methodName, parameterTypes.toArray(new Class[0]));
		} catch (NoSuchMethodException e) {
			// 解决多态参数获取不到method对象的问题。如签名参数类型为List，而传递参数类型为其子类LinkedList
			for (Method method : object.getClass().getDeclaredMethods()) {
				boolean isNameAndParamMatch = false;
				if (StringUtils.equals(method.getName(), methodName) && SizeUtil.size(method.getParameterTypes()) == SizeUtil.size(args)) {
					Class<?>[] parameterTypes2 = method.getParameterTypes();
					if (null != parameterTypes2) {
						for (int i = 0; i < parameterTypes2.length; i++) {
							if ((isNameAndParamMatch = parameterTypes2[i].isInstance(args[i])) == false) {
								break;
							}
						}
					}
				}
				if (isNameAndParamMatch) {
					return method;
				}
			}
		} catch (Exception e) {
			LOGGER.error(String.format("在对象'%s'上获取方法'%s'出错: '%s'", object, methodName, e.getMessage()), e);
		}
		return null;
	}

}
